/*
 *
 * Copyright (c) 2013 - 2020 Lijun Liao
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.xipki.security.util;

import org.bouncycastle.crypto.digests.SHA512Digest;
import org.bouncycastle.crypto.generators.DSAParametersGenerator;
import org.bouncycastle.crypto.params.DSAParameterGenerationParameters;
import org.bouncycastle.crypto.params.DSAParameters;

import java.math.BigInteger;
import java.security.SecureRandom;
import java.security.spec.DSAParameterSpec;
import java.util.HashMap;
import java.util.Map;

/**
 * Cache for DSA parameter specs.
 *
 * @author Lijun Liao
 * @since 2.0.0
 */
// CHECKSTYLE:SKIP
public final class DSAParameterCache {
  private static final Map<String, DSAParameterSpec> cache = new HashMap<>();

  /*
   * All pre-defined DSA parameters are generated using the following code
   * (based on bcprov-jdk15on v1.64):
   * and with JRE: openjdk version "1.8.0_252"
   *  OpenJDK Runtime Environment (build 1.8.0_252-8u252-b09-1~16.04-b09)
   *  OpenJDK 64-Bit Server VM (build 25.252-b09, mixed mode)
   *
   *   SecureRandom rnd = SecureRandom.getInstance("SHA1PRNG");
   *   rnd.setSeed(seed);
   *   DSAParametersGenerator gen = new DSAParametersGenerator(new SHA512Digest());
   *   DSAParameterGenerationParameters genParams =
   *       new DSAParameterGenerationParameters(plen, qlen, certainty, r);
   *   gen.init(genParams);
   *       DSAParameters params = gen.generateParameters();
   *
   * For the generation of the DSA parameters, the following values (in
   * hexadecimal representation) have been used as initial seed s:
   *
   *  Seed_p1024_q160 for p of 1024 bits and q of 160 bits:
   *  3243F6A8885A308D313198A2E03707344A409382
   *
   *  Seed_p2048_q224 for p of 2048 bits and q of 224 bits:
   *  2299F31D0082EFA98EC4E6C89452821E638D0137
   *
   *  Seed_p2048_q256 for p of 2048 bits and q of 256 bits:
   *  7BE5466CF34E90C6CC0AC29B7C97C50DD3F84D5B
   *
   *  Seed_p3072_q256 for p of 3072 bits and q of 256 bits:
   *   5B54709179216D5D98979FB1BD1310BA698DFB5A
   *
   * These seeds have been obtained as the first 4 substrings of 160-bit
   * length each of Q = Pi*2^1120, where Pi is the constant 3.14159...,
   * also known as Ludolph's number, i.e.,
   *
   *   Q = Seed_p1024_q160||Seed_p2048_q224||Seed_p2048_q256||Seed_p3072_q256||Remainder,
   *   where || denotes concatenation.
   *
   */

  static {
    // plen: 1024, qlen: 160
    String strP =
          "A2398EE147B3789DBA2293249A8CA39F217BDB2348DA08D9CB871DA896F41F96"
        + "F3544FFAD355F815F28A75542F1EE3BBD84A9A0B97E7D4290B6627700255A3AE"
        + "D2BDA39B58D1579BD82AB40EDE45CFAF394F55863A4A44D61A9CB992F3123D3E"
        + "632594BE053CD3FE60FCF9EC7CDD71ACB02D9162CFCC5C026658E48F48C11EB3";

    String strQ = "DCF35E20A2FBD43A22BE859971410E65387D566B";

    String strG =
          "8F3470A359ACB6845C4C65897234969FC73E3AE0EE8F245489EC03E4FDD544C3"
        + "54795ECF20EF72A4C52443B4C9B1A585589155B19ED1C25A03E29479CF6AB926"
        + "EA039752A1DFC23E274B8F2A62B2B38FE90DB00170F09EBC3E17B76EDE200E4F"
        + "428206E19868CFCCA4E800043DD70B50D2397B0EAE592B274F479DD09B033E1F";

    addDSAParamSpec(1024, 160, strP, strQ, strG);

    // plen: 2048, qlen: 224
    strP =
          "DF0C37CED38EB46E8DFF0A73A77495481483259FC91FBB097ADEFFC513598939"
        + "91B066B2463CDF31424B9B20A9D014558051004B76226393F3F0F25B200C357B"
        + "695C648F3856CF114720B7C399F21034F8E3478569655BFAD39A0CE24DCBC40B"
        + "582ADFA632EC5CBE87EC5028979015E337E4FE45D2507BF03ED59A14D1CB2474"
        + "BCE1F407755B02B1F0553CDE1BC9B80BE21E80685C020E56C4250D857A983CA2"
        + "BA3AE7365AC1807579463C4E4FA8AB01592B2A567B88E9BAD8DD8CF3FB23C367"
        + "8D209604013B5CAA3EB0D439B09894CBCCCCC51DC9A0D66425E574C9F9B4AACC"
        + "87BA745CCFF24D322876C6DC478C1E763E0F39380866CED1475D8EAEA2C79DB7";

    strQ = "F24D2D70B79B39FDB1387D85833F472997CA3C2683DD51E5CE07371B";

    strG =
          "18AA54DE3586FCFDF3F4447EA7EFC893FC3F53920121B730AF96563CAEE2E431"
        + "F40AD0627FEC3023FA1843DC0743183A841F5D7139F6A32FA6ADCE0EC6CB9D49"
        + "4416E3A09F343D08AC41A7F8F67D666C4B79496E8D12885A49BC342CE8F20C9C"
        + "0A299D6024788883D97B69A0C1626AEA0123971615C3EF439F2A7A4F416C531B"
        + "495298CAF97AE79AC70E0B496729B6AA2C27519055A7B6EF306CD2DD7B4D4457"
        + "46717927C22F8AD523FCE1F2101C0497E0DFEC8087466CBBB180F06AEBA5589A"
        + "2DA6385089BA0A38CACE8F560A181F921CF08D9B746801D9FE545B1B5BEF5BAE"
        + "350F6A752ED41F201B35CF55F5E0BCA7E8CFC08ACB2B44E3A31A99F898BE8624";

    addDSAParamSpec(2048, 224, strP, strQ, strG);

    // plen: 2048, qlen: 256
    strP =
          "D463E16BE51D40602C960703ED89FACBF5136869F4A7D3D4B995043A148ECDFF"
        + "8D3D135B7E8C3406A597EFE8E0079921B63803770F39F7560B0D15819C20AADD"
        + "64402FD6C990E07A98C0FA5773975572B5F70ABC058B63544E55FEF0CFCE6D22"
        + "2EC4AC06FD6CCAAA851324FA3535F768CFAAE4C27DAFBF02C6E1FB7C0D527075"
        + "951877BAEFA18FBD9AE354EF70BFBE57575F636EFC6FC9CFCC060E96D3B5D532"
        + "B21F777525FF0B888F902049E040EFB50D62D3B3458AEE01975A5636FD29660B"
        + "0D5A18C1DFFCBDA151DCCB70C6D40322F90A31326BDBF9936B4DC0EC62AA2DEF"
        + "337A2CE2A6179D7F64900EACE49E47795833E3CB3CB716BF110EA3B5F3F0FDCB";

    strQ = "86355AFD92DE59DE8AFCCF8A6C8AE873CE0A08B35F842CAF93ECBF38DB4F5CE9";

    strG =
          "48634B86C627CF33E45E9E8917A941E579A926D5A248EF9321C0FBD61FC2ED69"
        + "E559DC5B2DB9B19E687E01CFC22E547E44DB32ABCAB8128D4C124AAF13C1430D"
        + "A5D05149721AB328EF8E0EB4CCB2099E29C4AD15D098B2924BBEB232AC2C8F35"
        + "03BDAE4228312CB80B4263E20394B89BDB9EB5CDE9AA25F14882149DA56CD6A4"
        + "07DF8B75699BD434E60EB3DBE7D09D99E254AE1B27DDDF7B5FDE1AA474743F1A"
        + "9161FC424F6E3DBB1793890B39039DDA5B6C5D324FE130FBDBF3CC13F1CC2C92"
        + "F244CBC806F1589EECACE70CD53134CAAFE26D576057B43533EA78370C30AC63"
        + "AE561D7B06C5BC7E96B425F6D12715C3DA8C177C66125158ABBD2A512500C8BF";

    addDSAParamSpec(2048, 256, strP, strQ, strG);

    // plen: 3072, qlen: 256
    strP =
          "C915996D1DFD03D85F1E61092EA7CBE18852CCC227D2BB57130CF62E73DF6D45"
        + "0F63ABECECA084635CB283995943FF77DEA6F35E76C27DEDDB1499A244F6839D"
        + "20DC449953943950537808632B10C02309851FBD46731F4F8AF8D0BDE803833C"
        + "25C3699C489F18D63A83EF2EA48C09DFF94A7DE436A64E96D6B996EC078BA573"
        + "1F43BD0444DA18DAF07262F463447B8D0E15F9AB1047AFC232A10670D39F14B0"
        + "2DF9BED820F1B4F44B2C732E1AE0036D90EE12DF2B54B26E8C361325E56355DA"
        + "36D20774CDAFC4F85704313DF393B6DCB964EBA3DD09C9E33F1F511CE873E9D7"
        + "B467CA01C45E5FB96090959043A41F4B334C5912E040082765778A18934D2217"
        + "FC7865CE5E62543042AC7EF7D38DC282BB3E239528FEE0C15ABD4790B3D054B9"
        + "3D6F7830A2F6ECF5E377332EA77085194883FD59CBD7CA8DAF9C7CC43F2CC79D"
        + "FFEDD8BCF9103EB5424178CC7F6D80BB57CD1744D8D265D11C0BA7BE7AD06D82"
        + "9FE6391B9E5575A7C3BC87C8FC62D05FD10C7A0C8F1E6FAC92CAF7AD127DBE01";

    strQ = "980C693C7201AD7D5E93016DF531253845E7E28CDE4B371A9C342EEB7D71E165";

    strG =
          "8CEF95B6FBD1923BAA2CC95AC8770348C7128BF7233D5302C41272C1E3ABE976"
        + "6A39AEF48FDA0694910AE33E236B63374638B16665D606E1CD27D991092239B1"
        + "D7D689C43261E1322FD4E7968331502FCD6BD29EFCA0BE5D05FEABB70B0BFEB0"
        + "05546CFF75449D94991F832F06EC798960E60AEFB443C9B03F8BE4CE649ED0DE"
        + "932468656D35865514F84CF04780424C3F54E39BA77F60F225778B4F85CEDC60"
        + "C0E0E7BA7678040BE6D095B8AB96B90E3D62360142ECC60ECFB8CDB8F817D451"
        + "EFA5F58B61A89A8CC92A50470E1D127F19228B5945E09B3100D3997CE6F29ABE"
        + "2D5788BB1EC6B3AC798EFA99A9DCDEB87570CAD1DE4E6A89224A6C104F6D1CF1"
        + "BDB3B41FBA645475118B4548293E3F2B034A8CF153E792671B8BC1B7446FADD9"
        + "310E87A549E493C70ADFB799E3357EF8F2870F9880298F14A32C06AE538F957A"
        + "6891A896C50625B3D7A860E5FC7C8FE97F9542DA63F6DA6C39BA15C113630182"
        + "125DEC3A48A41A40C2067DD750DF478D202F472693E7ECBBA7D80B9F507AF61F";
    addDSAParamSpec(3072, 256, strP, strQ, strG);
  }

  private DSAParameterCache() {
  }

  // CHECKSTYLE:SKIP
  private static void addDSAParamSpec(int plen, int qlen, String strP, String strQ, String strG) {
    DSAParameterSpec spec = new DSAParameterSpec(new BigInteger(strP, 16),
        new BigInteger(strQ, 16), new BigInteger(strG, 16));
    cache.put(plen + "-" + qlen, spec);
  }

  // CHECKSTYLE:SKIP
  public static DSAParameterSpec getDSAParameterSpec(int plength, int qlength,
      SecureRandom random) {
    DSAParameterSpec spec = cache.get(plength + "-" + qlength);
    if (spec != null) {
      return new DSAParameterSpec(spec.getP(), spec.getQ(), spec.getG());
    }

    return getNewDSAParameterSpec(plength, qlength, random);
  }

  // CHECKSTYLE:SKIP
  public static DSAParameterSpec getNewDSAParameterSpec(int plength, int qlength,
      SecureRandom random) {
    final int certainty = 80;
    SecureRandom tmpRandom = (random == null) ? new SecureRandom() : random;
    DSAParametersGenerator paramGen = new DSAParametersGenerator(new SHA512Digest());
    DSAParameterGenerationParameters genParams =
        new DSAParameterGenerationParameters(plength, qlength, certainty, tmpRandom);
    paramGen.init(genParams);
    DSAParameters dsaParams = paramGen.generateParameters();
    return new DSAParameterSpec(dsaParams.getP(), dsaParams.getQ(), dsaParams.getG());
  }

}
